package com.yeziji.dynamicConfig.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.yeziji.dynamicConfig.base.ConfigMeta;
import com.yeziji.dynamicConfig.base.EnvironmentFactory;
import com.yeziji.dynamicConfig.msg.DynamicConfigErrorMsg;
import com.yeziji.dynamicConfig.service.ConfigService;
import com.yeziji.utils.expansion.Asserts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.data.util.ProxyUtils;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 被 configuration 修饰的实现类
 *
 * @author hwy
 * @since 2023/12/14 20:16
 **/
@Slf4j
public class ConfigurationPropertiesServiceImpl extends EnvironmentFactory implements ConfigService {
    private final String activeProfile;
    private final ApplicationContext applicationContext;


    public ConfigurationPropertiesServiceImpl(boolean printLog) {
        super(printLog);
        applicationContext = SpringUtil.getApplicationContext();
        activeProfile = applicationContext.getEnvironment().getActiveProfiles()[0];
    }

    @Override
    public boolean update(ConfigMeta configMeta) {
        String beanName = configMeta.getKey();
        try {
            Object bean = this.getRealBean(beanName);
            // 遍历键值对, 通过反射重新赋值
            Map<String, Object> properties = configMeta.getProperties();
            Object propertiesBean = BeanUtil.copyProperties(properties, bean.getClass());
            for (Map.Entry<String, Object> entry : properties.entrySet()) {
                String fieldName = entry.getKey();
                Object newValue = ReflectUtil.getFieldValue(propertiesBean, fieldName);
                ReflectUtil.setFieldValue(bean, fieldName, newValue);
            }
            return true;
        } catch (Exception e) {
            log.error("更新{}配置失败: {}", beanName, e.getMessage(), e);
        }
        return false;
    }

    @Override
    public boolean delete(ConfigMeta configMeta) {
        String beanName = configMeta.getKey();
        try {
            Object bean = this.getRealBean(beanName);
            // 遍历键值对, 通过反射重新赋值
            Class<?> clazz = bean.getClass();
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                declaredField.setAccessible(true);
                Object fieldVal = declaredField.get(bean);
                if (fieldVal instanceof Collection) {
                    ((Collection<?>) fieldVal).clear();
                } else {
                    declaredField.set(bean, null);
                }
            }
            return true;
        } catch (Exception e) {
            log.error("删除 {} 配置失败: {}", beanName, e.getMessage(), e);
        }
        return false;
    }

    @Override
    public ConfigMeta get(String key) {
        Map<String, Object> appConfigurationProperties = this.getAppConfigurationProperties();
        Object obj = appConfigurationProperties.get(key);
        if (obj != null) {
            return new ConfigMeta(activeProfile, key, JSONUtil.parseObj(obj));
        }
        return Asserts.error(DynamicConfigErrorMsg.CONFIG_IS_NOT_EXISTS);
    }

    @Override
    public List<ConfigMeta> listAll() {
        Map<String, Object> appConfigurationProperties = this.getAppConfigurationProperties();
        return appConfigurationProperties.entrySet().stream()
                .map(entry -> new ConfigMeta(activeProfile, entry.getKey(), JSONUtil.parseObj(entry.getValue())))
                .collect(Collectors.toList());
    }

    private Map<String, Object> getAppConfigurationProperties() {
        return applicationContext.getBeansWithAnnotation(ConfigurationProperties.class);
    }

    private Object getRealBean(String beanName) {
        Object bean = this.applicationContext.getBean(beanName);
        // 如果是 aop 动态代理就获取原本的代理目标 bean
        if (AopUtils.isAopProxy(bean)) {
            bean = ProxyUtils.getUserClass(bean);
        }
        return bean;
    }
}
